﻿using System;
using System.Collections.Generic;
using DependencyAnalysis;
using DependencyAnalysis.DataModel;
using DependencyAnalysis.Internal;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests
{
    [TestClass]
    public class VerifierTest
    {
        private Verifier _verifier;
        private IDependencyModel _dependencyModel;

        [TestInitialize]
        public void Initialize()
        {
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(1, 2, true, false);
            _dependencyModel = new DependencyModel();
            _dependencyModel.Add(descriptor);
            descriptor = DependencyModelTest.BuildDependencyDescriptor(1, 3, true, false);
            _dependencyModel.Add(descriptor);
            descriptor = DependencyModelTest.BuildDependencyDescriptor(2, 3, true, false);
            _dependencyModel.Add(descriptor);
            _verifier = new Verifier(_dependencyModel);
        }

        [TestMethod]
        public void WhenTargetIsNullRefersToThrowsArgumentNullException()
        {
            Assert1.Throws<ArgumentNullException>(() => _verifier.RefersTo<TypeDescriptor>(new TypeDescriptor(), null));
        }

        [TestMethod]
        public void WhenreferrerIsNullRefersToThrowsArgumentNullException()
        {
            Assert1.Throws<ArgumentNullException>(() => _verifier.RefersTo(null, new TypeDescriptor()));
        }

        [TestMethod]
        public void WhenTargetTypeIsNotFoundRefersToThrowsDependencyException()
        {
            Assert1.Throws<DependencyException>(() => _verifier.RefersTo(new TypeDescriptor(), new TypeDescriptor { FullName = "Dummy.Invalid" }));
        }

        [TestMethod]
        public void WhenTargetModuleIsNotFoundRefersToThrowsDependencyException()
        {
            Assert1.Throws<DependencyException>(
                () => _verifier.RefersTo(new TypeDescriptor(), new ModuleDescriptor { FullName = "Dummy.Invalid" }));
        }

        [TestMethod]
        public void WhenTargetNamespaceIsNotFoundRefersToThrowsDependencyException()
        {
            Assert1.Throws<DependencyException>(
                () => _verifier.RefersTo(new TypeDescriptor(), new SubmoduleDescriptor { FullName = "Dummy.Invalid" }));
        }

        [TestMethod]
        public void WhenUnsupportedTargetDescriptorRefersToThrowsArgumentException()
        {
            Assert1.Throws<ArgumentException>(() =>
                _verifier.RefersTo(new TypeDescriptor(), new DummyDescriptor { FullName = "Dummy.Invalid" }));
        }

        [TestMethod]
        public void WhenTargetTypeDependsOnreferrerTypeThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new TypeDescriptor { Identifier = "Module1.Submodule1.Type1" }, new TypeDescriptor { Identifier = "Module2.Submodule2.Type2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetTypeNotDependsOnreferrerTypeThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new TypeDescriptor { Identifier = "Module2.Submodule2.Type2" }, new TypeDescriptor { Identifier = "Module1.Submodule1.Type1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetTypeDependsOnreferrerModuleThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new ModuleDescriptor { Identifier = "Module1" }, new TypeDescriptor { Identifier = "Module2.Submodule2.Type2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetTypeNotDependsOnreferrerModuleThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new ModuleDescriptor { Identifier = "Module2" }, new TypeDescriptor { Identifier = "Module1.Submodule1.Type1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetTypeDependsOnreferrerSubmoduleThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new SubmoduleDescriptor { Identifier = "Module1.Submodule1" }, new TypeDescriptor { Identifier = "Module2.Submodule2.Type2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetTypeNotDependsOnreferrerSubmoduleThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new SubmoduleDescriptor { Identifier = "Module2.Submodule2" }, new TypeDescriptor { Identifier = "Module1.Submodule1.Type1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetTypeDependsOnreferrerMemberThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new MemberDescriptor { Identifier = "Module1.Submodule1.Type1.Member1" }, new TypeDescriptor { Identifier = "Module2.Submodule2.Type2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetTypeNotDependsOnreferrerMemberThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new MemberDescriptor { Identifier = "Module2.Submodule2.Type2.Member2" }, new TypeDescriptor { Identifier = "Module1.Submodule1.Type1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetSubmoduleDependsOnreferrerSubmoduleThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new SubmoduleDescriptor { Identifier = "Module1.Submodule1" }, new SubmoduleDescriptor { Identifier = "Module2.Submodule2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetSubmoduleNotDependsOnreferrerSubmoduleThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new SubmoduleDescriptor { Identifier = "Module2.Submodule2" }, new SubmoduleDescriptor { Identifier = "Module1.Submodule1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetSubmoduleDependsOnreferrerModuleThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new ModuleDescriptor { Identifier = "Module1" }, new SubmoduleDescriptor { Identifier = "Module2.Submodule2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetSubmoduleNotDependsOnreferrerModuleThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new ModuleDescriptor { Identifier = "Module2" }, new SubmoduleDescriptor { Identifier = "Module1.Submodule1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetSubmoduleDependsOnreferrerTypeThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new TypeDescriptor { Identifier = "Module1.Submodule1.Type1" }, new SubmoduleDescriptor { Identifier = "Module2.Submodule2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetSubmoduleNotDependsOnreferrerTypeThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new TypeDescriptor { Identifier = "Module2.Submodule2.Type2" }, new SubmoduleDescriptor { Identifier = "Module1.Submodule1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenSubmoduleNotRefersToTypeVerifyDoesNotRefersToThrowsNoException()
        {
            _verifier.VerifyDoesNotReferTo(new TypeDescriptor { Identifier = "Module2.Submodule2.Type2" }, new SubmoduleDescriptor { Identifier = "Module1.Submodule1" });
        }

        [TestMethod]
        public void WhenSubmoduleRefersToTypeVerifyDoesNotRefersToThrowsException()
        {
            Assert1.Throws<DependencyException>(
                () => _verifier.VerifyDoesNotReferTo(new SubmoduleDescriptor { Identifier = "Module1.Submodule1" }, new SubmoduleDescriptor { Identifier = "Module2.Submodule2" }));
        }

        [TestMethod]
        public void WhenTargetSubmoduleDependsOnreferrerMemberThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new MemberDescriptor { Identifier = "Module1.Submodule1.Type1.Member1" }, new SubmoduleDescriptor { Identifier = "Module2.Submodule2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetSubmoduleNotDependsOnreferrerMemberThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new MemberDescriptor { Identifier = "Module2.Submodule2.Type2.Member2." }, new SubmoduleDescriptor { Identifier = "Module1.Submodule1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetModuleDependsOnreferrerModuleThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new ModuleDescriptor { Identifier = "Module1" }, new ModuleDescriptor { Identifier = "Module2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetModuleNotDependsOnreferrerModuleThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new ModuleDescriptor { Identifier = "Module2" }, new ModuleDescriptor { Identifier = "Module1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetModuleDependsOnreferrerMemberThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new MemberDescriptor { Identifier = "Module1.Submodule1.Type1.Member1" }, new ModuleDescriptor { Identifier = "Module2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetModuleNotDependsOnreferrerMemberThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new MemberDescriptor { Identifier = "Module2.Submodule2.Type2.Member2" }, new ModuleDescriptor { Identifier = "Module1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetModuleDependsOnreferrerTypeThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new TypeDescriptor { Identifier = "Module1.Submodule1.Type1" }, new ModuleDescriptor { Identifier = "Module2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetModuleNotDependsOnreferrerTypeThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new TypeDescriptor { Identifier = "Module2.Submodule2.Type2" }, new ModuleDescriptor { Identifier = "Module1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetModuleDependsOnreferrerSubmoduleThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new SubmoduleDescriptor { Identifier = "Module1.Submodule1" }, new ModuleDescriptor { Identifier = "Module2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetModuleNotDependsOnreferrerSubmoduleThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new SubmoduleDescriptor { Identifier = "Module2.Submodule2" }, new ModuleDescriptor { Identifier = "Module1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetMemberDependsOnreferrerMemberThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new MemberDescriptor { Identifier = "Module1.Submodule1.Type1.Member1" }, new MemberDescriptor { Identifier = "Module2.Submodule2.Type2.Member2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetMemberNotDependsOnreferrerMemberThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new MemberDescriptor { Identifier = "Module2.Submodule2.Type2.Member2" }, new MemberDescriptor { Identifier = "Module1.Submodule1.Type1.Member1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetMemberDependsOnreferrerModuleThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new ModuleDescriptor { Identifier = "Module1" }, new MemberDescriptor { Identifier = "Module2.Submodule2.Type2.Member2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetMemberNotDependsOnreferrerModuleThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new ModuleDescriptor { Identifier = "Module2" }, new MemberDescriptor { Identifier = "Module1.Submodule1.Type1.Member1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetMemberDependsOnreferrerSubmoduleThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new SubmoduleDescriptor { Identifier = "Module1.Submodule1" }, new MemberDescriptor { Identifier = "Module2.Submodule2.Type2.Member2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetMemberNotDependsOnreferrerSubmoduleThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new SubmoduleDescriptor { Identifier = "Module2.Submodule2" }, new MemberDescriptor { Identifier = "Module1.Submodule1.Type1.Member1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenTargetMemberDependsOnreferrerTypeThenRefersToReturnsTrue()
        {
            var result = _verifier.RefersTo(new TypeDescriptor { Identifier = "Module1.Submodule1.Type1" }, new MemberDescriptor { Identifier = "Module2.Submodule2.Type2.Member2" });
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void WhenTargetMemberNotDependsOnreferrerTypeThenRefersToReturnsFalse()
        {
            var result = _verifier.RefersTo(new TypeDescriptor { Identifier = "Module2.Submodule2.Type2" }, new MemberDescriptor { Identifier = "Module1.Submodule1.Type1.Member1" });
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void WhenModuleIsNotUsedVerifyForeignModulesThrowsDependencyException()
        {
            var allowedModules = new List<string> { "Module2", "Module3", "NotAllowed" };
            Assert1.Throws<DependencyException>(() => _verifier.VerifyForeignModules(allowedModules));
        }

        [TestMethod]
        public void WhenModuleIsNotAllowedVerifyForeignModuleThrowsDependencyException()
        {
            var allowedModules = new List<string>();
            Assert1.Throws<DependencyException>(() => _verifier.VerifyForeignModules(allowedModules));
        }

        [TestMethod]
        public void WhenAllowedModulesAreUsedVerifyForeignModulesThrowsNoException()
        {
            var allowedModules = new List<string> { "Module2", "Module3" };
            Assert1.DoesNotThrow<DependencyException>(() => _verifier.VerifyForeignModules(allowedModules));
        }

        [TestMethod]
        public void NoOtherReferrersNoFalsePositivesTypeToModule()
        {
            _verifier.VerifyNoOtherReferrers(new List<IDescriptor> { new TypeDescriptor { Identifier = "Module1.Submodule1.Type1" } }, new ModuleDescriptor { Identifier = "Module2" });
        }

        [TestMethod]
        public void NoOtherReferrersNoFalseNegativesTypeToModule()
        {
            Assert1.Throws<DependencyException>(() =>
                _verifier.VerifyNoOtherReferrers(new List<IDescriptor> { new TypeDescriptor { Identifier = "Module3.Submodule3.Type3" } }, new ModuleDescriptor { Identifier = "Module2" }));
        }

        [TestMethod]
        public void NoOtherReferrersNoFalsePositivesMemberToSubmodule()
        {
            _verifier.VerifyNoOtherReferrers(new List<IDescriptor> { new MemberDescriptor { Identifier = "Module1.Submodule1.Type1.Member1" } }, new SubmoduleDescriptor { Identifier = "Module2.Submodule2" });
        }

        [TestMethod]
        public void NoOtherReferrersNoFalseNegativesMemberToSubmodule()
        {
            Assert1.Throws<DependencyException>(() =>
                _verifier.VerifyNoOtherReferrers(new List<IDescriptor> { new MemberDescriptor { Identifier = "Module3.Submodule3.Type3.Member3" } }, new SubmoduleDescriptor { Identifier = "Module2.Submodule2" }));
        }

        [TestMethod]
        public void NoOtherReferrersNoFalsePositivesModuleToMember()
        {
            _verifier.VerifyNoOtherReferrers(new List<IDescriptor> { new ModuleDescriptor { Identifier = "Module1" } }, new MemberDescriptor { Identifier = "Module2.Submodule2.Type2.Member2" });
        }

        [TestMethod]
        public void NoOtherReferrersNoFalseNegativesModuleToMember()
        {
            Assert1.Throws<DependencyException>(() =>
                _verifier.VerifyNoOtherReferrers(new List<IDescriptor> { new ModuleDescriptor { Identifier = "Module3" } }, new MemberDescriptor { Identifier = "Module2.Submodule2.Type2.Member2" }));
        }

        [TestMethod]
        public void VersatilityWhenTypeIsUsedOnlyOnceThrowException()
        {
            Assert1.Throws<DependencyException>(() => _verifier.VerifyVersatility(new TypeDescriptor { Identifier = "Module1.Submodule1.Type1" }, new List<IDescriptor>()));
        }

        [TestMethod]
        public void VersatilityWhenTypeIsUsedTwiceThrowNoException()
        {
            _verifier.VerifyVersatility(new TypeDescriptor { Identifier = "Module3.Submodule3.Type3" },
                                        new List<IDescriptor>());
        }

        [TestMethod]
        public void VersatilityWhenViolationIsExcludedThrowNoException()
        {
            _verifier.VerifyVersatility(new ModuleDescriptor { Identifier = "Module1" },
                                        new List<IDescriptor> { new TypeDescriptor { Identifier = "Module1.Submodule1.Type1" } });
        }

        [TestMethod]
        public void MembersAreUsedWhenNotUsedThrowException()
        {
            Assert1.Throws<DependencyException>(() =>
                _verifier.VerifyMembersAreUsed(new ModuleDescriptor { Identifier = "Module1" }, NodeVisibility.Ignore));
        }

        [TestMethod]
        public void MembersAreUsedWhenUsedThrowNoException()
        {
            _verifier.VerifyMembersAreUsed(new ModuleDescriptor { Identifier = "Module3" }, NodeVisibility.Ignore);
        }

        [TestMethod]
        public void WhenSubmodulesCorrectThenVerifySubmodulesThrowsNoException()
        {
            _verifier.VerifySubmodulesInModule(
                new ModuleDescriptor { Identifier = "Module3" }, new[]
                {
                    new SubmoduleDescriptor
                    {
                        Identifier = "Module3.Submodule3"
                    }
                });
        }

        [TestMethod]
        public void VerifySubmodulesIsCaseInsensitive()
        {
            _verifier.VerifySubmodulesInModule(
                new ModuleDescriptor { Identifier = "Module3" }, new[]
                {
                    new SubmoduleDescriptor
                    {
                        Identifier = "module3.submodule3"
                    }
                });
        }

        [TestMethod]
        public void WhenSubmodulesInorrectThenVerifySubmodulesThrowsException()
        {
            Assert1.Throws<DependencyException>(() => _verifier.VerifySubmodulesInModule(
                new ModuleDescriptor { Identifier = "Module3" }, new[]
                {
                    new SubmoduleDescriptor
                    {
                        Identifier = "Module3.Submodule17"
                    }
                }));
        }
    }

    internal class DummyDescriptor : IDescriptor
    {
        public string Identifier { get; set; }
        public string FullName { get; set; }
        public IDescriptor Parent { get; set; }
    }
}
